<?php

// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2025-04-05 21:25:32
// |@----------------------------------------------------------------------
// |@LastEditTime : 2025-04-26 18:58:35
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 控制器基类 - 扩展ThinkPHP控制器功能
// |@----------------------------------------------------------------------
// |@FilePath     : Controller.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2025 http://www.ladmin.cn   All rights reserved.
// ------------------------------------------------------------------------
declare (strict_types=1);

namespace think\admin;

use stdClass;
use think\App;
use think\Request;
use think\Response;
use think\helper\Str;
use ReflectionMethod;

/**
 * 控制器基类
 *
 * 本类为ThinkPHP控制器的扩展基类，提供以下增强功能：
 * 1. 注解驱动的权限验证体系（支持JWT、签名和双重验证）
 * 2. 自动参数验证（支持场景验证和混合验证）
 * 3. 服务自动加载机制
 * 4. 统一响应格式（标准化成功/错误响应）
 * 5. 反射方法缓存优化
 *
 * @package think\admin
 */
abstract class Controller extends stdClass
{
    /**
     * 应用实例
     * @var App
     */
    protected App $app;

    /**
     * 请求实例
     * @var Request
     */
    protected Request $request;

    /**
     * 构造函数
     *
     * 初始化控制器时自动执行：
     * 1. 权限验证检查
     * 2. 参数自动验证
     *
     * @param App $app ThinkPHP应用实例
     */
    public function __construct(App $app)
    {
        $this->app = $app;
        $this->request = $app->request;

        $this->module = $app->http->getName();
        $this->controller = class_basename(static::class);
        $this->action = $this->request->action();

        $this->checkAuth();
        $this->autoValidate();
    }

    /**
     * 解析方法注解
     *
     * 检查指定方法是否存在特定注解
     *
     * @param string $annotation 要检查的注解名称（不包含@符号）
     * @return bool 是否存在指定注解
     */
    protected function parseMethodAnnotation(string $annotation): bool
    {
        // 使用双层缓存：方法缓存 + 注解缓存
        static $annotationCache = [];
        $methodKey = "{$this->module}.{$this->controller}.{$this->action}";

        // 如果该方法的注解缓存不存在，则初始化
        if (!isset($annotationCache[$methodKey])) {
            $annotationCache[$methodKey] = [];
        }

        // 如果该方法的特定注解缓存不存在，则解析并缓存
        if (!isset($annotationCache[$methodKey][$annotation])) {
            $reflectionMethod = $this->getReflectionMethod();
            $methodDocComment = $reflectionMethod->getDocComment();

            // 缓存注解解析结果
            $annotationCache[$methodKey][$annotation] =
                ($methodDocComment !== false && preg_match("/@{$annotation}\s+/i", $methodDocComment));

            // 如果有注解值，进一步解析并缓存
            if ($annotationCache[$methodKey][$annotation]) {
                preg_match("/@{$annotation}\s+(\w+)/i", $methodDocComment, $matches);
                if (!empty($matches[1])) {
                    $annotationCache[$methodKey]["{$annotation}_value"] = $matches[1];
                }
            }
        }

        return $annotationCache[$methodKey][$annotation];
    }

    /**
     * 获取注解的值
     *
     * 新方法：直接获取注解的值而不只是布尔结果
     *
     * @param string $annotation 要获取值的注解名称
     * @param mixed $default 默认值
     * @return mixed 注解值或默认值
     */
    protected function getAnnotationValue(string $annotation, $default = null)
    {
        // 先检查注解是否存在
        if (!$this->parseMethodAnnotation($annotation)) {
            return $default;
        }

        // 从缓存中获取注解值
        static $annotationCache = [];
        $methodKey = "{$this->module}.{$this->controller}.{$this->action}";

        return $annotationCache[$methodKey]["{$annotation}_value"] ?? $default;
    }

    /**
     * 权限验证检查
     *
     * 支持以下验证模式（通过方法注解@auth指定）：
     * - jwt: 仅JWT验证
     * - sign: 仅签名验证
     * - both: 双重验证（JWT+签名）
     *
     * @throws \think\exception\HttpResponseException 验证失败时抛出异常
     */
    protected function checkAuth()
    {
        if (!$this->parseMethodAnnotation('auth')) {
            return;
        }

        // 使用优化后的注解值获取方法
        $authMode = $this->getAnnotationValue('auth', 'none');

        switch ($authMode) {
            case 'jwt':
                if (!$this->request->user) {
                    $this->error(401, lang('request.jwt_unauthorized'));
                }
                break;

            case 'sign':
                $this->autoSign();
                break;

            case 'both':
                if (!$this->request->user) {
                    $this->error(401, lang('request.jwt_unauthorized'));
                }
                $this->autoSign();
                break;
        }
    }

    /**
     * 自动签名验证
     *
     * 签名验证流程：
     * 1. 过滤空值参数
     * 2. 检查签名参数是否存在
     * 3. 验证用户登录状态
     * 4. 生成服务端签名并与客户端签名比对
     *
     * @throws \think\exception\HttpResponseException 验证失败时抛出异常
     */
    public function autoSign()
    {
        if ($this->request->isGet() || $this->request->user) {
            return;
        }

        $param = array_filter($this->request->param(), function ($value) {
            return $value !== "" && $value !== null;
        });

        if (!array_key_exists('sign', $param)) {
            $this->error(PARAM_MISSING, lang('request.auth_missing_sign_parameter'));
        }

        unset($param['sign']);

        if (!$this->request->user) {
            $this->error(LOGIN_EXPIRED, lang('request.auth_login_expired'));
        }

        $param['salt'] = $this->request->user['salt'] ?? '';
        ksort($param, SORT_NATURAL);

        foreach ($param as &$v) {
            if (is_array($v)) {
                $v = str_replace('\/', '/', json_encode($v, JSON_UNESCAPED_UNICODE));
            }
            if (is_bool($v)) {
                $v = $v ? "true" : "false";
            }
        }

        $sign = hash('sha256', join(',', $param));
        $clientSign = $this->request->param('sign');

        if ($clientSign !== $sign) {
            $this->error(SIGN_INVALID, lang('request.auth_invalid_parameter'));
        }
    }

    /**
     * 自动参数验证
     *
     * 根据方法注解自动验证请求参数：
     * - @validate true: 启用基础验证
     * - @mixed validate true: 根据id参数区分添加/编辑场景
     * - @password validate true: 根据password参数区分批量/单个操作
     *
     * @throws \think\exception\HttpResponseException 验证失败时抛出异常
     */
    protected function autoValidate()
    {
        if ($this->request->isGet()) {
            return;
        }

        if (!$this->parseMethodAnnotation('validate')) {
            return;
        }

        $param = $this->request->param();
        $validateClass = $this->app->parseClass('validate', $this->controller);
        if (!class_exists($validateClass)) {
            $this->error(CLASS_NOT_EXISTS, lang('system.error_class_not_exists', ['class' => $validateClass]));
        }

        $validate = $this->app->make($validateClass);
        $scene = ucfirst($this->action);

        // 使用优化后的注解检查方法
        if ($this->parseMethodAnnotation('mixed')) {
            $scene = empty($param['id']) ? "{$this->action}Add" : "{$this->action}Edit";
        }
        if ($this->parseMethodAnnotation('password')) {
            $scene = empty($param['password']) ? "{$this->action}Batch" : "{$this->action}";
        }

        if (!$validate->hasScene($scene)) {
            $this->error(SCENE_NOT_EXISTS, lang('system.error_scene_not_exists', ['class' => $validateClass, 'scene' => "scene{$scene}"]));
        }

        if (!$validate->scene($scene)->check($param)) {
            $this->error(VALIDATION_FAILED, $validate->getError());
        }
    }

    /**
     * 获取当前请求的反射方法
     *
     * 使用缓存机制优化反射性能
     *
     * @return ReflectionMethod 当前请求方法的反射实例
     */
    protected function getReflectionMethod(): ReflectionMethod
    {
        // 使用应用级静态缓存而非请求级缓存
        static $methodCache = [];
        $key = "{$this->module}.{$this->controller}.{$this->action}";

        if (!isset($methodCache[$key])) {
            $controllerClass = "app\\{$this->module}\\controller\\". Str::studly($this->controller);
            $methodCache[$key] = new ReflectionMethod($controllerClass, $this->action);
        }

        return $methodCache[$key];
    }

    /**
     * 魔术方法-服务定位
     *
     * 通过属性访问自动加载服务类，格式为：service + 服务名
     * 例如：$this->serviceUser 会自动加载 User 服务类
     *
     * @param string $name 属性名
     * @return mixed 服务类实例
     * @throws \think\exception\HttpResponseException 服务类不存在时抛出异常
     */
    public function __get(string $name)
    {
        if (preg_match('/^service(\w+)$/i', $name, $matches)) {
            $className = Str::studly($matches[1]);
            return $this->loadService($className);
        }
    }

    /**
     * 加载服务类
     *
     * 按照以下顺序查找服务类：
     * 1. think\admin\service 命名空间下
     * 2. 应用自定义服务类
     *
     * @param string $className 服务类名
     * @return mixed 服务类实例
     * @throws \think\exception\HttpResponseException 服务类不存在时抛出异常
     */
    protected function loadService(string $className)
    {
        $services = [
            "\\think\\admin\\service\\{$className}",
            $this->app->parseClass('service', $className)
        ];
        foreach ($services as $class) {
            if (class_exists($class)) {
                return $this->app->make($class);
            }
        }

        $this->error(500, lang('system.error_service_not_found', ['class' => $className]));
    }

    /**
     * 成功响应
     *
     * 标准化成功响应格式：
     * {
     *     "code": 200,
     *     "message": "操作成功",
     *     "data": {}
     * }
     *
     * @param mixed $data 响应数据
     * @param string $message 成功消息（可选，默认使用语言包中的消息）
     * @return Response 标准化响应对象
     */
    public function success($data = null, string $message = ''): Response
    {
        throw_response(
            code: [
                'code' => 200,
                'message' => $message ?: lang('system.success_operation'),
                'data' => $data
            ],
            httpCode: 200 // 成功响应固定使用 HTTP 200
        );
    }

    /**
     * 错误响应
     *
     * 标准化错误响应格式：
     * {
     *     "code": 错误码,
     *     "message": "错误消息",
     *     "data": {}
     * }
     *
     * 注意：大多数业务错误仍然使用 HTTP 200 状态码
     * 只有几种特殊情况（未授权401、禁止访问403、未找到404、服务器错误500）
     * 才会返回对应的 HTTP 状态码
     *
     * @param int|array $code 错误码或错误数组（包含code、message等字段）
     * @param string $message 错误消息（可选）
     * @param mixed $data 错误数据（可选）
     * @return Response 标准化响应对象
     */
    public function error($code = BAD_REQUEST, string $message = '', $data = null): Response
    {
        $params = is_array($code) ? $code : [
            'code' => $code,
            'message' => $message ?: lang('system.error_operation'),
            'data' => $data
        ];

        // 根据业务错误码确定 HTTP 状态码
        $businessCode = $params['code'];
        $httpCode = 200; // 默认使用 200

        // 特殊业务错误码映射到对应的 HTTP 状态码
        if ($businessCode === UNAUTHORIZED) {
            $httpCode = 401;
        } elseif ($businessCode === FORBIDDEN) {
            $httpCode = 403;
        } elseif ($businessCode === NOT_FOUND) {
            $httpCode = 404;
        } elseif ($businessCode === SERVER_ERROR) {
            $httpCode = 500;
        }

        // 如果需要指定特殊的 HTTP 状态码，可以在参数中明确设置
        if (isset($params['http_code'])) {
            $httpCode = $params['http_code'];
        }

        throw_response($params, httpCode: $httpCode);
    }
}
